#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: perms 52 2009-01-28 08:45:34Z urzenia $

__version__   = 'version 0.5'
__author__    = 'Marcin ``MySZ`` Sztolcman <marcin@urzenia.net>'
__copyright__ = '(r) 2005-2009'
__program__   = 'perms - tool for recursive change permissions or owner files and/or directories'
__date__      = '2006-12-11'
__license__   = 'GPL v.2'

__desc__      = '''%(desc)s
%(author)s %(copyright)s
license: %(license)s
version %(version)s (%(date)s)''' % {
  'desc': __program__,
  'author': __author__,
  'copyright': __copyright__,
  'license': __license__,
  'version': __version__,
  'date': __date__
}

STANDARD_F='644'
STANDARD_D='755'



import getopt
import grp
import os
import os.path
import pwd
import sys

usage = dict (
  s = u'Usage: %(name)s [--dir-perm=perm] [--file-perm=perm] [--standard] [--both-perm=perm] [--owner=name] [--owner-affect=all|file|dir] [--group=group] [--group-affect=all|file|dir] [--omit-rootdir] [--version] [--help] directory' % dict (name=os.path.basename (sys.argv[0])),
  l = '''Usage: %(name)s [--options] directory

OPTIONS:
  -x|--both-perm oct_dir_file_perms
      if given, set file-perm and dir-perm equal to oct_dir_file_perms
  -o|--owner owner_name
      if given, files and/or directories (look: -a) will be changed to owner_name, in other case - will not change
  -s|--standard default: -f 644 -d 755, for change edit %(name)s
  -a|--owner-affect all|file|dir
      specify affection for -o option. if given 'all' (default), will change owner of files and directories. if 'file', will change only files owner, and if 'dir' - only directories owner
  -g|--group group_name
      if goven, files and/or directories (look: -b) will be changed to group_name, in other case - will not change
  -b|--group-affect all|file|dir
      the same as '-a', but works for group, not for owner
  -r|--omit-rootdir don't affect 'directory' directory (doesn't matter for 'directory' childs)
  -v|--version
      prints program name and exits
  -h|--help
''' % dict (name=os.path.basename (sys.argv[0]))
)

class Settings (object):
  __s = {}
  def __setattr__ (self, k, v):
    if self.__s.has_key (k):
      self.__s[k] = v
  def __getattr__ (self, k):
    return self.__s[k]
  def __delattr__ (self, k):
    pass
  def __init__ (self, **v):
    if v is not None:
      self.__s.update (v)
  def __str__ (self):
    return "\n".join ( ["%s: %s" % i for i in self.__s.items ()] )

def exit (msg, code = 0):
  print usage['s']
  if msg:
    print
    print >>sys.stderr, msg
  raise SystemExit, code

def chmod (path, v, mode):
  try:
    os.chmod (path, getattr (v, mode))
    return True
  except OSError:
    return False

def chown (path, v, mode):
  try:
    os.chown (path, v.uid, -1)
    return True
  except OSError:
    return False

def chgrp (path, v, mode):
  try:
    os.chown (path, -1, v.gid)
    return True
  except OSError:
    return False

def main ():
  s_opt = 'd:f:o:g:a:b:vhx:rs'
  l_opt = ('dir-perm=', 'file-perm=', 'owner=', 'group=', 'owner-affect=', 'group-affect=', 'version', 'help', 'both-perm=', 'omit-rootdir', 'standard')
  try:
    opts, args = getopt.gnu_getopt (sys.argv[1:], s_opt, l_opt)
  except getopt.GetoptError, msg:
    exit (msg, 1)

  v = Settings (
    path = os.getcwdu (),
    dir = None,
    file = None,
    uid = None,
    gid = None,
    oaffect = 'all',
    gaffect = 'all',
    omitroot = False,
    standard = False,
  )
  for o, a in opts:
    if o in ('-d', '--dir-perm'):
      v.dir = int (a, 8)
    elif o in ('-f', '--file-perm'):
      v.file = int (a, 8)
    elif o in ('-s', '--standard'):
      v.standard = True
    elif o in ('-x', '--both-perm'):
      v.file = int (a, 8)
      v.dir  = int (a, 8)
    elif o in ('-o', '--owner'):
      v.uid = a.strip ()
    elif o in ('-g', '--group'):
      v.gid = a.strip ()
    elif o in ('-a', '--owner-affect'):
      if a not in ('all', 'file', 'dir'):
        exit ('Option -a must be one of: all, file, dir.', 2)
      v.oaffect = a
    elif o in ('-b', '--group-affect'):
      if a not in ('all', 'file', 'dir'):
        exit ('Option -b must be one of: all, file, dir.', 3)
      v.gaffect = a
    elif o in ('-r', '--omit-rootdir'):
      v.omitroot = True
    elif o in ('-v', '--version'):
      print __desc__
      raise SystemExit, 0
    elif o in ('-h', '--help'):
      print usage['l']
      raise SystemExit, 0

  if len (args) >= 1:
    v.path = os.path.expanduser (args[0])
    v.path = os.path.abspath (v.path)

  if v.uid is not None:
    try:
      v.uid = pwd.getpwnam (v.uid)[2]
    except KeyError:
      exit ('Cannot find user \'%s\'.' % v.uid, 3)

  if v.gid is not None:
    try:
      v.gid = grp.getgrnam (v.gid)[2]
    except KeyError:
      exit ('Cannot find group \'%s\'.' % v.gid, 4)

  #czy ktorykolwiek tryb jest wybrany ?
  if v.gid is None and v.uid is None and\
      v.dir is None and v.file is None and v.standard == False:
    exit ('Work mode must be at least one of: \'-u\', \'-g\', \'-d\', \'-f\', \'-s\' or \'-x\'.', 5)

  actions = dict (
    d = [],
    f = []
  )
  if v.standard:
    v.dir  = int (STANDARD_D, 8)
    v.file = int (STANDARD_F, 8)
  if v.gid is not None:
    if v.gaffect in ('dir', 'all'):
      actions['d'].append (chgrp)
    if v.gaffect in ('file', 'all'):
      actions['f'].append (chgrp)
  if v.uid is not None:
    if v.oaffect in ('dir', 'all'):
      actions['d'].append (chown)
    if v.oaffect in ('file', 'all'):
      actions['f'].append (chown)
  if v.dir is not None:
    actions['d'].append (chmod)
  if v.file is not None:
    actions['f'].append (chmod)


  #jedziemy z koksem...
  omitted = []

  #najpierw dla katalogu nadrzednego
  if not v.omitroot:
    for action in actions['d']:
      if not action (v.path, v, 'dir'):
        omitted.append (v.path)

  for root, dirs, files in os.walk (v.path):
    #katalogi
    for _d in dirs:
      for action in actions['d']:
        path = os.path.join (root, _d)
        if not action (path, v, 'dir'):
          omitted.append (path)

    for _f in files:
      for action in actions['f']:
        path = os.path.join (root, _f)
        if not action (path, v, 'file'):
          omitted.append (path)

  if omitted:
    print 'Failed:'
    print "\n".join (omitted)


if __name__ == '__main__':
  main ()

'''
ChangeLog:
2006-12-11  - v.0.5 - added support for options: -s, -r
2006-09-15  - v.0.4 - added support for option: -x
                    - refactorized
2005-08-10  - v.0.3 - rewritten
                    - added support for options: -o, -a, -g, -b
2005-04-16          - cosmetics
2005-03-23  - v.0.2 - clean code
                    - cosmetics
'''

# vim: ft=python
